package com.ctbri.containerindepth;

import java.util.AbstractMap;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashSet;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.ListIterator;
import java.util.Map;
import java.util.Set;

import com.ctbri.util.Countries;

public class SimpleHashMap_Ex25<K, V> extends AbstractMap<K, V> {
	
	static class Entry<K, V> implements Map.Entry<K, V> {
		private K key;
		private V value;
		Entry<K, V> next;
		
		public Entry(K key, V value) {
			this.key = key;
			this.value = value;
		}
		
		public K getKey() {
			return key;
		}
		
		public V getValue() {
			return value;
		}
		
		public V setValue(V v) {
			V result = value;
			value = v;
			return result;
		}
		
		public int hashCode() {
			return key.hashCode() ^
					(value == null ? 0 : value.hashCode());
		}
		
		public boolean equals(Object o) {
			if(o instanceof Entry) {
				@SuppressWarnings("unchecked")
				Entry<K, V> e = (Entry<K, V>)o;
				Object key1 = getKey();
				Object key2 = e.getKey();
				if(key1.equals(key2)) {
					Object val1 = getValue();
					Object val2 = e.getValue();
					return val1 == null ? val2 == null : val1.equals(val2);
				}
			}
			return false;
		}
		
		public String toString() {
			return key + "=" + value;
		}
	}
	
	
	static final int SIZE = 997;
	
	@SuppressWarnings("unchecked")
	Entry<K, V>[] buckets = new Entry[SIZE];

	@Override
	public V put(K key, V value) {
		V oldValue = null;
		int index = Math.abs(key.hashCode()) % SIZE;
		Entry<K, V> newPair = new Entry<K, V>(key, value);
		if(buckets[index] == null) 
			buckets[index] = newPair;
		Entry<K, V> prevPair = null;
		boolean found = false;
		for(Entry<K, V> pair = buckets[index] ; pair != null; pair = pair.next) {
			if(pair.getKey().equals(key)){
				oldValue = pair.getValue();
				if(prevPair != null)
					prevPair.next = newPair;
				else
					buckets[index] = newPair;
				newPair.next = pair.next;
				found = true;
				break;
			}
			prevPair = pair;
		}
		if(!found)
			prevPair.next = newPair;
		
		return oldValue;
	}
	
	@Override
	public V get(Object key) {
		int index= Math.abs(key.hashCode()) % SIZE;
		for(Entry<K, V> pair = buckets[index]; pair != null; pair = pair.next) {
			if(pair.getKey().equals(key))
				return pair.getValue();
		}
		return null;
	}
	
	@Override
	public Set<Map.Entry<K, V>> entrySet() {
		Set<Map.Entry<K, V>> set = new HashSet<>();
		for(Entry<K,V> bucket : buckets) {
			for(Entry<K,V> pair = bucket; pair != null; pair = pair.next) {
				set.add(pair);
			}
		}
		return set;
	}
	
	@SuppressWarnings("unchecked")
	@Override
	public void clear() {
		buckets = new Entry[SIZE];
	}
	
	public V remove(Object key) {
		int index = Math.abs(key.hashCode()) % SIZE;
		Entry<K, V> prePair = null;
		for(Entry<K, V> pair = buckets[index]; pair != null; pair = pair.next) {
			if(pair.getKey().equals(key)) {
				V value = pair.getValue();
				if(prePair != null)
					prePair.next = pair.next;
				else
					buckets[index] = null;
				return value;
			}
		}
		return null;
	}
	
	public int size() {
		int size = 0;
		for(Entry<K,V> bucket : buckets) {
			for(Entry<K,V> pair = bucket; pair != null; pair = pair.next) {
				size++;
			}
		}
		return size;
	}
	
	public boolean isEmpty() {
		for(Entry<K,V> bucket : buckets) {
			if(bucket != null)
				return false;
		}
		return true;
	}
	
	public boolean containsKey(Object key) {
		for(Entry<K,V> bucket : buckets) {
			for(Entry<K,V> pair = bucket; pair != null; pair = pair.next) {
				if(pair.getKey().equals(key))
					return true;
			}
		}
		return false;
	}
	
	public static void main(String[] args) {
		SimpleHashMap_Ex25<String, String> m = new SimpleHashMap_Ex25<>();
		m.putAll(Countries.capitals(25));
		System.out.println(m);
		System.out.println(m.get("ERITREA"));
		System.out.println(m.entrySet());
	}

}
